﻿using System;
using System.Linq;
using System.Collections;
using T0yK4T.IO;
using ProtoBuf;
using T0yK4T.Data;
using System.Collections.Generic;
using MongoDB;

namespace ToyChat.Server
{
    /// <summary>
    /// Used to manage <see cref="IChannel"/>s
    /// </summary>
    public class ChannelHandler : IChannelHandler
    {
        private Dictionary<int, IChannel> loadedChannels = new Dictionary<int, IChannel>();
        private int nextChannelID = int.MinValue;
        private IServer owner;
        private IChannel defaultChannel;

        /// <summary>
        /// Initializes a new instance of <see cref="ChannelHandler"/>
        /// </summary>
        /// <param name="owner">The <see cref="IServer"/> that owns this ChannelHandler</param>
        public ChannelHandler(IServer owner)
        {
            this.owner = owner;
            this.LoadChannels();
        }

        private void LoadChannels()
        {
            IEnumerable<Channel> storedChannels = this.owner.GlobalDBHandler.GetChannels();
            foreach (Channel channel in storedChannels)
            {
                if (this.loadedChannels.ContainsKey(channel.ChannelID)) // Channel exists, delete
                    this.owner.GlobalDBHandler.DeleteChannel(channel);
                else
                {
                    this.loadedChannels.Add(channel.ChannelID, channel);
                }
            }
            if (!this.loadedChannels.TryGetValue(1, out this.defaultChannel))
            {
                this.defaultChannel = new Channel() { ChannelID = 1, ChannelName = "Default", MotD = "Default Channel (unconfigured)...", Password = string.Empty };
                this.owner.GlobalDBHandler.StoreChannel(defaultChannel);
                this.loadedChannels.Add(defaultChannel.ChannelID, defaultChannel);
            }
            this.nextChannelID = this.GetNextChannelID(int.MinValue);
        }

        private int GetNextChannelID(int offset)
        {
            while (this.owner.GlobalDBHandler.FindChannel(offset) != null)
                ++offset;

            if (offset == 1 || offset == -1)
                return this.GetNextChannelID(2);

            return offset;
        }

        /// <summary>
        /// Attempts to find a channel with the specified name
        /// </summary>
        /// <param name="channelName">The name of the channel to find</param>
        /// <returns>The channel request if it exists, otherwise null</returns>
        public IChannel this[string channelName]
        {
            get
            {
                IChannel idChannel = this.owner.GlobalDBHandler.FindChannel(channelName);
                if (idChannel != null)
                    return this[idChannel.ChannelID];
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets the channel with the specified ID if it exists
        /// </summary>
        /// <param name="channelID">The ID of the channel to find</param>
        /// <returns>The channel with the specified ID if it exists, otherwise null</returns>
        public IChannel this[int channelID]
        {
            get
            {
                IChannel channel = null;
                if (this.loadedChannels.TryGetValue(channelID, out channel))
                    return channel;
                else
                {
                    channel = this.owner.GlobalDBHandler.FindChannel(channelID);
                    this.loadedChannels.Add(channel.ChannelID, channel);
                }
                return channel;
            }
        }

        /// <summary>
        /// Attempts to create a new channel with the specified channelname
        /// </summary>
        /// <param name="channelName">The name of the channel to create</param>
        /// <param name="channel">Will contain the channel that was created</param>
        /// <returns>True if succesful, otherwise false</returns>
        public bool CreateChannel(string channelName, out IChannel channel)
        {
            channel = null;
            if (string.IsNullOrEmpty(channelName) || channelName.Trim().Length < 1)
                return false;

            this.nextChannelID = this.GetNextChannelID(this.nextChannelID);
            Channel tchannel = new Channel();
            tchannel.ChannelID = this.nextChannelID;
            tchannel.ChannelName = channelName.Trim();
            tchannel.Password = string.Empty;
            tchannel.MotD = string.Empty;
            if (this.owner.GlobalDBHandler.FindChannel(tchannel.ChannelName) != null)
                return false;
            else
            {
                this.owner.GlobalDBHandler.StoreChannel(tchannel);
                this.loadedChannels.Add(tchannel.ChannelID, tchannel);
                channel = tchannel;
                return true;
            }
        }

        /// <summary>
        /// Calls <see cref="IDatabaseHandler.StoreChannel(IChannel)"/>
        /// <para/>
        /// (Saves changes to the specified channel object)
        /// </summary>
        /// <param name="channel">The channel to save</param>
        public void UpdateDB(IChannel channel)
        {
            this.owner.GlobalDBHandler.StoreChannel(channel);
        }

        /// <summary>
        /// Deletes the specified channel
        /// </summary>
        /// <param name="channel">The channel to delete</param>
        public void DeleteChannel(IChannel channel)
        {
            if (channel.ChannelID == 1)
                throw new InvalidOperationException("Cannot delete default channel!");
            this.loadedChannels.Remove(channel.ChannelID);
            this.owner.GlobalDBHandler.DeleteChannel(channel);
        }

        /// <summary>
        /// Removes the specified user from all channels
        /// </summary>
        /// <param name="user">The user to remove</param>
        public void RemoveUser(IUser user)
        {
            foreach (IChannel channel in this)
                channel.RemoveUser(user);
        }

        /// <summary>
        /// Attempts to get a channel with the specified ChannelID
        /// </summary>
        /// <param name="channelID">The ChannelID to look for</param>
        /// <param name="channel">If succesful, will be set to the instance of <see cref="IChannel"/> that was found</param>
        /// <returns>True if succesful, otherwise false</returns>
        public bool TryGetChannel(int channelID, out IChannel channel)
        {
            try { channel = this[channelID]; }
            catch { channel = null; }
            return channel != null;
        }

        /// <summary>
        /// Attempts to get a channel with the specified channel name
        /// </summary>
        /// <param name="channelName">The name of the channel to look for</param>
        /// <param name="channel">If succesful, will be set to the instance of <see cref="IChannel"/> that was found</param>
        /// <returns>True if succesful, otherwise false</returns>
        public bool TryGetChannel(string channelName, out IChannel channel)
        {
            try { channel = this[channelName]; }
            catch { channel = null; }
            return channel != null;
        }

        /// <summary>
        /// Gets an <see cref="IEnumerator{IChannel}"/> that can be used to iterate through the internal channel list
        /// </summary>
        /// <returns></returns>
        public IEnumerator<IChannel> GetEnumerator()
        {
            return this.loadedChannels.Values.GetEnumerator();
        }

        /// <summary>
        /// Gets an <see cref="IEnumerator"/> that can be used to iterate through the internal channel list
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.loadedChannels.Values.GetEnumerator();
        }

        /// <summary>
        /// Gets the default channel
        /// </summary>
        public IChannel DefaultChannel
        {
            get { return this.defaultChannel; }
        }
    }
}